/**
 * Copyright (c) 2012 Nokia Corporation.
 */

package com.nokia.example.gfxsensorsdemo.graphics;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.m3g.Appearance;
import javax.microedition.m3g.Background;
import javax.microedition.m3g.Camera;
import javax.microedition.m3g.Graphics3D;
import javax.microedition.m3g.Image2D;
import javax.microedition.m3g.IndexBuffer;
import javax.microedition.m3g.Light;
import javax.microedition.m3g.Material;
import javax.microedition.m3g.Texture2D;
import javax.microedition.m3g.Transform;
import javax.microedition.m3g.TriangleStripArray;
import javax.microedition.m3g.VertexArray;
import javax.microedition.m3g.VertexBuffer;

import com.nokia.example.gfxsensorsdemo.engine.MyModel;
import com.nokia.example.gfxsensorsdemo.gestures.SafeGestureEvent;
import com.nokia.example.gfxsensorsdemo.gestures.SafeGestureInteractiveZone;
import com.nokia.example.gfxsensorsdemo.gestures.SafeGestureListener;
import com.nokia.example.gfxsensorsdemo.gestures.SafeGestureRegistrationManager;

/**
 * The application canvas containing the cube.
 */
public class MyCanvas extends Canvas implements SafeGestureListener {
	// Constants
	private static final int CAMERA_DISTANCE_INTERVAL = 2;
	private static final int MIN_CAMERA_DISTANCE = 20;
	private static final int MAX_CAMERA_DISTANCE = 80;
	
	// Members
    private Graphics3D      iG3D;
    private Camera          iCamera;
    private Light           iLight;
    private Transform       iTransform = new Transform();
    private Background      iBackground = new Background();
    private VertexBuffer    iVb;    // positions, normals, colors, texcoords
    private IndexBuffer     iIb;    // indices to iVB, forming triangle strips
    private Appearance      iAppearance; // material, texture, compositing, ...
    private Material        iMaterial = new Material();
    private Image           iImage;
    
    private MyModel _model= null;
    private float[] _angles = null;
    private float[] _velocity = null;
    private StringBuffer _textBuffer = new StringBuffer();
    private float _camDistance = 40.0f;

    /**
     * Construct the Displayable.
     */
    public MyCanvas() {
    	_model = new MyModel();
    	_angles = new float[3];
    	
        try {
            init();
        }
        catch(Exception e) {
             e.printStackTrace();
        }
        
        // Register to listen to the pinch event
        // Note: If the device doesn't have multitouch support, this will cause
        // unhandled exception.
        SafeGestureRegistrationManager.setListener(this, this);
        
        SafeGestureInteractiveZone gestureZone = new SafeGestureInteractiveZone();
        gestureZone.setGesture(SafeGestureInteractiveZone.GESTURE_PINCH);
        gestureZone.setRectangle(0, 0, this.getWidth(), this.getHeight());

        SafeGestureRegistrationManager.register(this, gestureZone);
    }

    /**
     * Component initialization.
     */
    private void init() throws Exception  {
        // add the Exit command
        addCommand(new Command("Exit", Command.EXIT, 1));

        // get the singleton Graphics3D instance
        iG3D = Graphics3D.getInstance();

        // create a camera
        System.out.println("MyCanvas::init(): Creating camera...");
        iCamera = new Camera();
        iCamera.setPerspective( 60.0f,              // field of view
            (float)getWidth()/ (float)getHeight(),  // aspectRatio
            1.0f,      // near clipping plane
            1000.0f ); // far clipping plane

        // create a light
        System.out.println("MyCanvas::init(): Creating light...");
        iLight = new Light();
        iLight.setColor(0xffffff);         // white light
        iLight.setIntensity(1.25f);          // overbright

        // init some arrays for our object (cube)

        // Each line in this array declaration represents a triangle strip
        // for one side of a cube. The only primitive we can draw with is the
        // triangle strip so if we want to make a cube with hard edges we
        // need to construct one triangle strip per face of the cube.
        // 1 * * * * * 0
        //   * *     *
        //   *   *   *
        //   *     * *
        // 3 * * * * * 2
        // The ASCII diagram above represents the vertices in the first line
        // (the first tri-strip)
        short[] vert = {
            10, 10, 10,  -10, 10, 10,   10,-10, 10,  -10,-10, 10,   // front
           -10, 10,-10,   10, 10,-10,  -10,-10,-10,   10,-10,-10,   // back
           -10, 10, 10,  -10, 10,-10,  -10,-10, 10,  -10,-10,-10,   // left
            10, 10,-10,   10, 10, 10,   10,-10,-10,   10,-10, 10,   // right
            10, 10,-10,  -10, 10,-10,   10, 10, 10,  -10, 10, 10,   // top
            10,-10, 10,  -10,-10, 10,   10,-10,-10,  -10,-10,-10 }; // bottom

        // create a VertexArray to hold the vertices for the object
        System.out.println("MyCanvas::init(): Creating vertex array...");
        VertexArray vertArray = new VertexArray(vert.length / 3, 3, 2);
        vertArray.set(0, vert.length/3, vert);

        // The per-vertex normals for the cube; these match with the vertices
        // above. Each normal is perpendicular to the surface of the object at
        // the corresponding vertex.
        byte[] norm = {  
            0, 0, 127,    0, 0, 127,    0, 0, 127,    0, 0, 127,
            0, 0,-127,    0, 0,-127,    0, 0,-127,    0, 0,-127,
           -127, 0, 0,   -127, 0, 0,   -127, 0, 0,   -127, 0, 0,
            127, 0, 0,    127, 0, 0,    127, 0, 0,    127, 0, 0,
            0, 127, 0,    0, 127, 0,    0, 127, 0,    0, 127, 0,
            0,-127, 0,    0,-127, 0,    0,-127, 0,    0,-127, 0 };

        // create a vertex array for the normals of the object
        System.out.println("MyCanvas::init(): Creating vertex array for the normals...");
        VertexArray normArray = new VertexArray(norm.length / 3, 3, 1);
        normArray.set(0, norm.length/3, norm);

        // per vertex texture coordinates
        short[] tex = {  
            1, 0,       0, 0,       1, 1,       0, 1,
            1, 0,       0, 0,       1, 1,       0, 1,
            1, 0,       0, 0,       1, 1,       0, 1,
            1, 0,       0, 0,       1, 1,       0, 1,
            1, 0,       0, 0,       1, 1,       0, 1,
            1, 0,       0, 0,       1, 1,       0, 1 };

        // create a vertex array for the texture coordinates of the object
        System.out.println("MyCanvas::init(): Creating vertex array for texture...");
        VertexArray texArray = new VertexArray(tex.length / 2, 2, 2);
        texArray.set(0, tex.length/2, tex);

        // the length of each triangle strip
        int[] stripLen = { 4, 4, 4, 4, 4, 4 };

        // create the VertexBuffer for our object
        System.out.println("MyCanvas::init(): Creating vertex buffer...");
        VertexBuffer vb = iVb = new VertexBuffer();
        vb.setPositions(vertArray, 1.0f, null);      // unit scale, zero bias
        vb.setNormals(normArray);
        vb.setTexCoords(0, texArray, 1.0f, null);    // unit scale, zero bias

        // create the index buffer for our object (this tells how to
        // create triangle strips from the contents of the vertex buffer).
        iIb = new TriangleStripArray( 0, stripLen );

        // load the image for the texture
        iImage = Image.createImage("/borg-cube-128.png");
        //iImage = Image.createImage("/texture.png");

        // create the Image2D (we need this so we can make a Texture2D)
        Image2D image2D = new Image2D( Image2D.RGB, iImage );

        // create the Texture2D

        // texture color is to be modulated with the lit material color
        System.out.println("MyCanvas::init(): Creating texture...");
        Texture2D texture = new Texture2D( image2D );
        texture.setFiltering(Texture2D.FILTER_NEAREST,
                             Texture2D.FILTER_NEAREST);
        texture.setWrapping(Texture2D.WRAP_CLAMP,
                            Texture2D.WRAP_CLAMP);
        texture.setBlending(Texture2D.FUNC_MODULATE);

        // create the appearance
        System.out.println("MyCanvas::init(): Creating appearance...");
        iAppearance = new Appearance();
        iAppearance.setTexture(0, texture);
        iAppearance.setMaterial(iMaterial);
        iMaterial.setColor(Material.DIFFUSE, 0xFFFFFFFF);   // white
        iMaterial.setColor(Material.SPECULAR, 0xFFFFFFFF);  // white
        iMaterial.setShininess(100.0f);

        //iBackground.setColor(0xffffcc); // set the background color
        iBackground.setColor(0x000000); // set the background color
    }

    /**
     * Paint the scene.
     */
    protected void paint(Graphics g) {

        // Bind the Graphics of this Canvas to our Graphics3D. The
        // viewport is automatically set to cover the entire clipping
        // rectangle of the Graphics object. The parameters indicate
        // that z-buffering, dithering, and true color rendering are
        // enabled, but antialiasing is disabled.
        iG3D.bindTarget(g, true,
                        Graphics3D.DITHER |
                        Graphics3D.TRUE_COLOR);

        // clear the color and depth buffers
        iG3D.clear(iBackground);

        // set up the camera in the desired position
        Transform transform = new Transform();
        transform.postTranslate(0.0f, 0.0f, _camDistance);
        iG3D.setCamera(iCamera, transform);

        // set up a "headlight": a directional light shining
        // from the direction of the camera
        iG3D.resetLights();
        iG3D.addLight(iLight, transform);

        // update our transform (this will give us a rotating cube)
        // Get the velocity from the model and modify the cube angles based on
        // the values.
        _model.calculateVelocity();
        _velocity = _model.velocity();
        
        for (int i = 0; i < 3; ++i) {
        	_angles[i] += _velocity[i];
        }
        
        iTransform.setIdentity();
        iTransform.postRotate(_angles[1], 1.0f, 0.0f, 0.0f);
        iTransform.postRotate(_angles[0], 0.0f, 0.0f, 1.0f);

        // Render our cube. We provide the vertex and index buffers
        // to specify the geometry; the appearance so we know what
        // material and texture to use; and the transform to tell
        // where to render the object
        iG3D.render(iVb, iIb, iAppearance, iTransform);

        // flush
        iG3D.releaseTarget();
        
        _textBuffer.delete(0, _textBuffer.length());
        
        for (int i = 0; i < 2; ++i) {
        	_textBuffer.append(_velocity[i]);
        	_textBuffer.append(" ");
        }
        
        g.setColor(0xffffffff);
        g.drawString(_textBuffer.toString(), 6, 6, Graphics.TOP | Graphics.LEFT);
    }

    /**
     * Handles pinch gestures and changes the distance of the camera based on
     * the gesture. 
     */
	public void gestureAction(Object container,
			SafeGestureInteractiveZone gestureInteractiveZone,
			SafeGestureEvent gestureEvent)
	{
        if (gestureEvent.getType() == SafeGestureInteractiveZone.GESTURE_PINCH) {
            if (gestureEvent.getPinchDistanceChange() < 0) {
                // If the gesture was inwards, scale smaller
            	if (_camDistance < MAX_CAMERA_DISTANCE) {
            		_camDistance += CAMERA_DISTANCE_INTERVAL;
            	}
            }
            else if (gestureEvent.getPinchDistanceChange() > 0) {
                // If the gesture was outwards, scale larger
            	if (_camDistance > MIN_CAMERA_DISTANCE) {
            		_camDistance -= CAMERA_DISTANCE_INTERVAL;
            	}
            }
        }		
	}
}

